/**
 *  \file sections.c
 *
 *  \brief
 *
 *  \author   <@linde-mh.de>
 *
 *
 *
 *
 *
 */

/*===============================================[ private includes  ]================================================*/
#define SECTIONS_LINKAGE

#include "sections.h"
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
/*===============================================[ private defines   ]================================================*/

/*===============================================[ private datatypes ]================================================*/

/*===============================================[ private variables ]================================================*/

/*===============================================[ private functions ]================================================*/
/**
 * This function merges all sections within 'src', which lie within the 'bounds' section. Undefined memory addresses are
 * filled with the 'fillByte'.
 * Attention: 'seclist' has to be sorted for this function to work.
 */
RC sections_merge(struct section_list* seclist, struct section* bounds, U8 fillByte, BO minimalFill);

/*====================================================================================================================*/

struct section_list* section_list_create() {
   struct section_list* seclist;
   if (NULL != (seclist = malloc(sizeof(struct section_list)))) {
      LIST_INIT(seclist);
   }
   return seclist;
}

struct section* section_create(U32 size) {
   struct section* sec;
   if (NULL != (sec = malloc(sizeof(struct section) + size))) {
      LIST_DATA(sec)->size_max = size;
      LIST_DATA(sec)->size = 0;
   }
   return sec;
}

void section_list_delete(struct section_list* seclist) {
   if (NULL != seclist) {
      section_list_clear(seclist);
      free(seclist);
   }
}

void section_delete(struct section* sec) {
   if (NULL != sec) {
      LIST_UNLINK(sec);
      free(sec);
   }
}

void section_list_clear(struct section_list* seclist) {
   if (NULL != seclist) {
      while (!LIST_IS_EMPTY(seclist)) {
         section_delete(LIST_FIRST(seclist));
      }
   }
}

void section_insert(struct section_list *seclist, struct section *sec) {
   struct section *p;
   struct section *node = NULL;

   LIST_FOR_EACH(seclist, p) {
      if (LIST_DATA(p)->address > LIST_DATA(sec)->address) {
         node = p;
         break;
      }
   }
   if (NULL == node) {
      LIST_PUSH_BACK( seclist, sec);
   } else {
      LIST_INSERT( node, sec);
   }
}

void sections_sort(struct section_list *seclist) {
   BO sorted;
   struct section* sec;
   struct section* nextSec;

   /* bubble sort */
   do {
      sorted = TRUE;

      LIST_FOR_EACH(seclist, sec) {
         nextSec = LIST_NEXT(sec);

         /* check whether 'sec' and 'nextSec' are in the right order */
         if (nextSec != NULL && LIST_DATA(sec)->address > LIST_DATA(nextSec)->address) {
            struct section* predecessor = sec->prev;
            struct section* successor = nextSec->next;

            /* current state:       [predecessor]<--->[sec]<--->[nextSec]<--->[successor] */

            /* swap 'sec' and 'nextSec' */
            predecessor->next = nextSec;
            nextSec->prev = predecessor;
            nextSec->next = sec;
            sec->prev = nextSec;
            sec->next = successor;
            successor->prev = sec;

            /* new state:           [predecessor]<--->[nextSec]<--->[sec]<--->[successor] */

            sorted = FALSE;
            sec = sec->prev; /* go back in the list, so that 'sec' vs. 'successor' is the next comparison */
         }
      }
   } while (FALSE == sorted);

   return;
}

void sections_fill(struct section_list* seclist, U8 fillByte) {
   struct section* sec;
   LIST_FOR_EACH(seclist, sec) {
      memset(&LIST_DATA(sec)->data, (int) fillByte, LIST_DATA(sec)->size_max);
      LIST_DATA(sec)->size = LIST_DATA(sec)->size_max;
   }
}

void sections_enforce_bounds(struct section_list* seclist, struct section_list* boundaries) {

   struct section* sec;
   struct section* boundarySec;
   U32 sectionStart;
   U32 sectionEnd;
   U32 boundaryStart;
   U32 boundaryEnd;
   BO can_stay;

   /* iterate over all sections from 'seclist' */
   sec = LIST_FIRST(seclist);
   while (sec != NULL ) {
      sectionStart = LIST_DATA(sec)->address;
      sectionEnd = LIST_DATA(sec)->address + LIST_DATA(sec)->size - 1;
      can_stay = FALSE;

      /* iterate over all boundary sections to find a matching one */
      boundarySec = LIST_FIRST(boundaries);
      while (boundarySec != NULL && !can_stay) {
         boundaryStart = LIST_DATA(boundarySec)->address;
         boundaryEnd = LIST_DATA(boundarySec)->address + LIST_DATA(boundarySec)->size - 1;

         /* CASE A: section starts before boundary section and reaches into the boundary section */
         if (sectionStart < boundaryStart && sectionEnd >= boundaryStart) {

            /* split off the part of the section that lies before the boundary section */
            U32 cutOffLength = boundaryStart - sectionStart;
            struct section* cutOffSection = section_create(cutOffLength);
            memcpy(LIST_DATA(cutOffSection)->data, LIST_DATA(sec)->data, cutOffLength);
            LIST_DATA(cutOffSection)->address = sectionStart;
            LIST_DATA(cutOffSection)->size = cutOffLength;
            LIST_PUSH_BACK(seclist, cutOffSection);

            /* truncate existing section to remove the part that is now in the section that was split off */LIST_DATA(sec)->address =
                  boundaryStart;
            LIST_DATA(sec)->size -= cutOffLength;
            memmove(LIST_DATA(sec)->data, &(LIST_DATA(sec)->data[cutOffLength]), LIST_DATA(sec)->size);
            sectionStart = boundaryStart;
         }

         /* CASE B: section starts before boundary section end and reaches out of the boundary section */
         if (sectionStart <= boundaryEnd && sectionEnd > boundaryEnd) {

            /* split off the part of the section that lies after the boundary section */
            U32 cutOffLength = sectionEnd - boundaryEnd;
            struct section* cutOffSection = section_create(cutOffLength);
            memcpy(LIST_DATA(cutOffSection)->data, &(LIST_DATA(sec)->data[LIST_DATA(sec)->size - cutOffLength]),
                  cutOffLength);
            LIST_DATA(cutOffSection)->address = boundaryEnd + 1;
            LIST_DATA(cutOffSection)->size = cutOffLength;
            LIST_PUSH_BACK(seclist, cutOffSection);

            /* truncate existing section to remove the part that is now in the section that was split off */
            LIST_DATA(sec)->size -= cutOffLength;
            sectionEnd -= cutOffLength;
         }

         /* the section (which was possibly truncated) can stay, if it does fit into the boundary section */
         if (sectionStart >= boundaryStart && sectionStart <= boundaryEnd && sectionEnd >= boundaryStart
               && sectionEnd <= boundaryEnd) {
            can_stay = TRUE;
         }

         boundarySec = LIST_NEXT(boundarySec);
      }

      if (FALSE == can_stay) {
         printf("\tWARNING: Section at address 0x%X with size ", (unsigned int) sectionStart);
         printf("0x%X is\n\t         undefined in segmentation ", (unsigned int)(sectionEnd - sectionStart + 1));
         printf("file and will be discarded.\n");
         struct section* oldsec = sec;
         sec = LIST_NEXT(sec);
         section_delete(oldsec);
      } else {
         sec = LIST_NEXT(sec);
      }
   }
}

RC sections_merge(struct section_list* seclist, struct section* bounds, U8 fillByte, BO minimalFill) {
   if (NULL == bounds) {
      return RC_ERROR;
   }

   struct section* firstSec;
   struct section* lastSec;
   struct section* newSec;
   struct section* tempSec;

   U32 offset = 0;

   U32 secStart;
   U32 secEnd;
   U32 boundsStart = LIST_DATA(bounds)->address;
   U32 boundsEnd = LIST_DATA(bounds)->address + LIST_DATA(bounds)->size - 1;

   /* find first section from 'seclist' which lies within the 'bounds' */
   firstSec = LIST_FIRST(seclist);
   while (NULL != firstSec) {
      secStart = LIST_DATA(firstSec)->address;
      secEnd = LIST_DATA(firstSec)->address + LIST_DATA(firstSec)->size - 1;

      if (secStart >= boundsStart && secStart <= boundsEnd && secEnd <= boundsEnd) {
         /* we found the first section within the 'bounds' */
         break;
      } else {
         firstSec = LIST_NEXT(firstSec);
      }
   }

   if (NULL != firstSec) {
      /* find the last section from 'seclist' which lies within the 'bounds' */
      lastSec = firstSec;
      while( NULL != (tempSec = LIST_NEXT(lastSec)) ) {
         secStart = LIST_DATA(tempSec)->address;
         secEnd = LIST_DATA(tempSec)->address + LIST_DATA(tempSec)->size - 1;

         if (secStart >= boundsStart && secStart <= boundsEnd && secEnd <= boundsEnd) {
            lastSec = tempSec;
         } else {
            /* tempSec is the first section that is outside the 'bounds' --> lastSec is still within the 'bounds' */
            break;
         }
      }
   }

   /* calculate the start and end of the new, combined memory section */
   if (FALSE == minimalFill) {
      /* if the user wants that the new, combined section has exactly the size of the 'bounds' section */
      secStart = boundsStart;
      secEnd = boundsEnd;
   } else {
      /* if the user wants the new, combined section to be as small as possible (minimalFill = true) */
      if (NULL == firstSec) {
         /* none of the existing sections fit into the 'bounds', so we create a new section with size 0*/
         secStart = boundsStart;
         secEnd = boundsStart - 1;
      } else {
         /* some sections fit into the 'bounds', so we create a new section which spans all these sections */
         secStart = LIST_DATA(firstSec)->address;
         secEnd = LIST_DATA(lastSec)->address + LIST_DATA(lastSec)->size - 1;
      }
   }

   /* try to allocate combined memory segment */
   if (NULL == (newSec = section_create(secEnd - secStart + 1))) {
      printf("\tError: Could not create a section struct for merging of memory sections.\n");
      return RC_ERROR;
   }

   /* initialize combined memory segment */
   memset(LIST_DATA(newSec)->data, (int) fillByte, LIST_DATA(newSec)->size_max);
   LIST_DATA(newSec)->address = secStart;
   LIST_DATA(newSec)->size = LIST_DATA(newSec)->size_max;

   /* iterate over all sections between 'firstSec' and 'lastSec' and copy their contents to 'newSec' */
   BO done;

   if (NULL == firstSec) {
      done = TRUE;
   } else {
      done = FALSE;
   }

   while (FALSE == done) {
      offset = LIST_DATA(firstSec)->address - secStart;
      memcpy(&(LIST_DATA(newSec)->data[offset]), LIST_DATA(firstSec)->data, LIST_DATA(firstSec)->size);

      if (firstSec == lastSec) {
         done = TRUE;
      }

      tempSec = firstSec;
      firstSec = LIST_NEXT(firstSec);
      section_delete(tempSec);
   }

   section_insert(seclist, newSec);

   return RC_SUCCESS;
}

RC sections_maximize(struct section_list* seclist, struct section_list* boundaries, U8 fillByte, BO minimalFill) {
   if (NULL == boundaries) {
      if (NULL == seclist || NULL == LIST_FIRST(seclist)) {
         return RC_SUCCESS;
      }
   }

   BO tempBoundaries = FALSE;
   struct section* sec;
   sections_sort(seclist);

   /* if no boundaries were given, simply create a temporary boundary list with a single large boundary segment */
   if (boundaries == NULL) {
      if (NULL == (boundaries = section_list_create())) {
         printf("\tError: Could not create temporary segment list for segment maximization.\n");
         return RC_ERROR;
      }

      U32 startAddress = LIST_DATA(LIST_FIRST(seclist))->address;
      U32 endAddress = LIST_DATA(LIST_LAST(seclist))->address + LIST_DATA(LIST_LAST(seclist))->size - 1;

      if (NULL == (sec = section_create(endAddress - startAddress + 1))) {
         printf("\tError: Could not create temporary segment for segment maximization.\n");
         section_list_delete(boundaries);
         return RC_ERROR;
      }

      LIST_DATA(sec)->address = startAddress;
      section_insert(boundaries, sec);
      sections_fill(boundaries, fillByte);
      tempBoundaries = TRUE;        /* signal that the boundaries list is only temporary */
   }

   /* iterate over all boundary segments */
   LIST_FOR_EACH(boundaries, sec) {
      if (RC_ERROR == sections_merge(seclist, sec, fillByte, minimalFill)) {
         printf("\Error: Could not merge memory sections.");

         /* delete the boundary list if it was only a temporary list */
         if (TRUE == tempBoundaries) {
            section_list_delete(boundaries);
         }
         return RC_ERROR;
      }
   }

   /* delete the boundary list if it was only a temporary list */
   if (TRUE == tempBoundaries) {
      section_list_delete(boundaries);
   }

   return RC_SUCCESS;
}

#if 0
void * sections_dump( struct section_list *seclist , U8 padding, U32 start_address, U32 size, void *data )
{
   if( NULL == data)
   {
      data = malloc(size);
      if( NULL == data )
      {
         return NULL;
      }
   }

   {
      struct section *p;
      struct section *node=NULL;
      U32 last;
      U32 last_address=0;
      U32 start_copy;
      U32 to_fill;
      LIST_FOR_EACH( seclist, p)
      {
         U32 sec_last_address = LIST_DATA(p)->address + LIST_DATA(p)->size -1;

         address_range( )

         if( sec_last_address >= start_address )
         {
            U32 to_fill = LIST_DATA(p)->address - start_address;
            U32 to_copy = size-to_fill;

            if( to_copy > LIST_DATA(p)->size-to_fill )
            {
               to_copy = LIST_DATA(p)->size-to_fill;
            }

            memset( &data[last], padding, to_fill );

            memcpy( &data[ last ], LIST_DATA(p)->data[ start_copy ], to_copy );

            last=
            node=p;

            /* if list contains more data then neccessary */
            if( LIST_DATA(p)->address > start_address+size )
            {
               break;
            }
         }
      }
      /* fill reset */
      memset( &data[last], padding, to_fill );
   }

}
#endif

/***********************************************************************************************************************
 *                             Copyright 2010 Linde Material Handling. All rights reserved.                             *
 ***********************************************************************************************************************/
